﻿//
// © Algobox Ltd - Benn Eichhorn
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Algobox.Toolbox.Collections.TimeSeries
{
    public class BarLookBack
    {
        #region Constructors

        public BarLookBack(int numberOfLookbackBars, int pricesPerBar)
        {
            _arraySize = numberOfLookbackBars + 1;
            _pricesPerBar = pricesPerBar;

            _highs = new double[_arraySize];
            _lows = new double[_arraySize];
            _priceCount = new int[_arraySize];
        }

        #endregion
        //////////////////
        #region Member Variables

        readonly int _arraySize;
        readonly int _pricesPerBar;

        double[] _highs;
        double[] _lows;
        int[] _priceCount;

        int _currentIndex;
        int _barCounter;

        double _high;
        double _low;

        #endregion
        //////////////////
        #region Properties


        public double High { get { return _high; } }

        public double Low { get { return _low; } }

        public double HighCurrent { get { return _highs[_currentIndex]; } }

        public double LowCurrent { get { return _lows[_currentIndex]; } }

        public bool HasData { get { if (_barCounter >= _arraySize - 1) return true; return false; } }

        /// <summary>
        /// Number of prices per bar
        /// </summary>
        public int BarSize { get { return _pricesPerBar; } }

        /// <summary>
        /// Number of bars in series
        /// </summary>
        public double NumBars { get { return _arraySize - 1; } }

        #endregion
        //////////////////
        #region Functions

        /// <summary>
        /// Try to add a price and/or volume to specific time in the timeseries
        /// </summary>
        /// <param name="timeStamp">The time stamp of the price to be added</param>
        /// <param name="price">The price to be added at this time in the timeseries</param>
        /// <param name="volume">The volume to be added at this time to the timeseries</param>
        /// <returns>Returns the 0-indexed bar number that this price has been entered into</returns>
        public int Add(DateTime timeStamp, double price, ulong volume = 0)
        {
            if (_priceCount[_currentIndex] == _pricesPerBar)
            {
                // generate a new bar
                _barCounter++;
                _currentIndex++;
                if (_currentIndex >= _arraySize)
                    _currentIndex = 0;

                _priceCount[_currentIndex] = 0;
                _highs[_currentIndex] = 0;
                _lows[_currentIndex] = 0;

                // recalc high
                _high = 0;
                for (int i = 0; i < _arraySize; i++)
                {
                    if (i != _currentIndex)
                        _high = System.Math.Max(_high, _highs[i]);
                }

                // recalc low
                if (_currentIndex != 0)
                    _low = _lows[0];
                else
                    _low = _lows[1];

                double low;
                _low = Double.MaxValue;
                for (int i = 0; i < _arraySize; i++)
                {
                    low = _lows[i];
                    if (i != _currentIndex && low != 0)
                        _low = System.Math.Min(_low, low);
                }
            }

            _priceCount[_currentIndex]++;

            if (price > _highs[_currentIndex])
            {
                _highs[_currentIndex] = price;
            }
            if (price != 0 && (_lows[_currentIndex] == 0 || price < _lows[_currentIndex]))
            {
                _lows[_currentIndex] = price;
            }

            return _barCounter;
        }

        public bool TryGetPreviousHighLow(int numberOfBars, out double high, out double low)
        {
            if (numberOfBars >_barCounter)
            {
                high = 0;
                low = 0;
                return false;
            }
            if (_currentIndex > 0)
            {
                high = _highs[_currentIndex - 1];
                low = _lows[_currentIndex - 1];
            }
            else
            {
                high = _highs[_arraySize - 1];
                low = _lows[_arraySize - 1];
            }
            for (int i = _currentIndex - 1; numberOfBars-- > 0; i--)
            {
                if (i < 0)
                    i = _arraySize - 1;

                low = System.Math.Min(low, _lows[i]);
                high = System.Math.Max(high, _highs[i]);
            }
            return true;
        }

        #endregion
    }
}
